﻿using System;
using System.Security.Principal; //WindowsIdentity TokenImpersonatioLevel
using System.Net.Security; //NegotiateStream
using System.Net; //NetworkCredential
using System.Diagnostics;
using System.Threading;

namespace negoserverjni
{
    public class Spnego
    {
        internal static TraceSource log = new TraceSource("Spnego");

        NegotiateStream negostream = null;
        const byte well = 20;
        InsideStream s;
        IAsyncResult resultAuthentication;
        byte[] tokenreceived;
        byte[] tokenToSend;
        RequestState requeststatus = new RequestState();

        Header h;

        Status status;
        WindowsIdentity identity;
        public Spnego()
        {
            s = new InsideStream(this);
        }
        
        public byte[] acceptSecurityContext(byte[] token, out string Name)
        {
            Name = null;
            status = Status.HandshakeError;

            if (token == null || token.Length == 0 || token.Length > UInt16.MaxValue) return null;
            tokenreceived = token;
            if (negostream == null)
            {
                negostream = new NegotiateStream(s);
                NetworkCredential cred = new NetworkCredential();
                resultAuthentication = negostream.BeginAuthenticateAsServer(CredentialCache.DefaultNetworkCredentials, ProtectionLevel.None, TokenImpersonationLevel.Identification, null, null);
            }
            else
            {
                InsideAsyncResult result = new InsideAsyncResult();
                result.AsyncState = requeststatus.State;
                result.IsCompleted = true;
                result.CompletedSynchronously = false;
                requeststatus.Continuation(result);
            }
            if (h == null) throw new ArgumentNullException("header is null");

            status = h.AuthenticationStatus;
            if (status != Status.HandshakeInProgress)
            {
                try
                {
                    negostream.EndAuthenticateAsServer(resultAuthentication);
                    if (negostream.IsAuthenticated)
                    {
                        identity = (WindowsIdentity)negostream.RemoteIdentity;
                        Name = identity.Name;
                        log.TraceEvent(TraceEventType.Verbose,Thread.CurrentThread.ManagedThreadId,identity.Name);
                    }
                }
                catch(Exception e)
                {
                    log.TraceEvent(TraceEventType.Verbose,Thread.CurrentThread.ManagedThreadId,"Error : " + e.GetType() + " " + e.Message);
                }
            }//TO DO
            if (tokenToSend == null || tokenToSend.Length == 0)
                log.TraceEvent(TraceEventType.Verbose,Thread.CurrentThread.ManagedThreadId,"acceptSecurityContext: tokenToSend is empty");
            else log.TraceEvent(TraceEventType.Verbose,Thread.CurrentThread.ManagedThreadId,"acceptSecurityContext: Length of tokenToSend: {0}", tokenToSend.Length);
            byte[] l_tokenToSend = tokenToSend;
            tokenToSend = null;
            return l_tokenToSend;
        }
        public IAsyncResult SendToNegoStream(byte[] buffer, int offset, int count, AsyncCallback callback, Object state)
        {
            log.TraceEvent(TraceEventType.Verbose,Thread.CurrentThread.ManagedThreadId,"SendToNegoStream");
            if (count == 5)
                if( requeststatus.IsFirstRoundTrip)
                {
                    log.TraceEvent(TraceEventType.Verbose, Thread.CurrentThread.ManagedThreadId, "SendToNegoStream: First BeginRead  tokenReceived: {0}", tokenreceived.Length);
                    Header h = new Header((uint)tokenreceived.Length);
                    byte[] header = h.ToBuffer();
                    Array.Copy(header, 0, buffer, offset, 5);
                    log.TraceEvent(TraceEventType.Verbose, Thread.CurrentThread.ManagedThreadId, "SendToNegoStream: header content Length :{0}", h.Length);
                    requeststatus.SaveState(buffer, offset, count, callback, state);
                    log.TraceEvent(TraceEventType.Verbose, Thread.CurrentThread.ManagedThreadId, "SendToNegoStream: Synchronous return ");

                    return InsideAsyncResult.Synchronous(state);
                }
                else
                {
                    requeststatus.SaveState(buffer, offset, count, callback, state);
                    log.TraceEvent(TraceEventType.Verbose, Thread.CurrentThread.ManagedThreadId, "SendToNegoStream :First BeginRead Bis");
                    log.TraceEvent(TraceEventType.Verbose, Thread.CurrentThread.ManagedThreadId, "SendToNegoStream: Async return ");

                    return InsideAsyncResult.Async(state);
                }
            else
            {
                log.TraceEvent(TraceEventType.Verbose, Thread.CurrentThread.ManagedThreadId, "SendToNegoStream: BeginRead : count != 5");
                if (count == tokenreceived.Length)
                {
                    Array.Copy(tokenreceived, 0, buffer, offset, count);
                    requeststatus.SaveState(buffer, offset, count, callback, state);
                    log.TraceEvent(TraceEventType.Verbose, Thread.CurrentThread.ManagedThreadId, "SendToNegoStream: Synchronous return ");

                    return InsideAsyncResult.Synchronous(state);

                }
                else
                {
                    string msg = string.Format("SendToNegoStream: count {0} != tokenreceived.Length {1}", count, tokenreceived.Length);
                    log.TraceEvent(TraceEventType.Verbose,Thread.CurrentThread.ManagedThreadId,msg);
                    throw new ArgumentException(msg); 
                }
            }
        }
        public int EndSendToNegoStream(IAsyncResult result)
        {
            if ((requeststatus.Length == 5) && (!requeststatus.IsFirstRoundTrip))
            {
                byte[] header = new Header((uint)tokenreceived.Length).ToBuffer();
                Array.Copy(header, 0, requeststatus.Buffer, requeststatus.Offset, 5);
            }
            log.TraceEvent(TraceEventType.Verbose,Thread.CurrentThread.ManagedThreadId,"EndSendToNegoStream rc: {0}",requeststatus.Length);
            return requeststatus.Length;
        }
        
        public IAsyncResult ReceiveFromNegoStream(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
        {
            log.TraceEvent(TraceEventType.Verbose,Thread.CurrentThread.ManagedThreadId,"ReceiveFromNegoStream");
            if (count == 5)
            {
                byte[] b = buffer;
                if (offset != 0)
                {
                    b = new byte[5];
                    Array.Copy(buffer,offset,b, 0, count);
                }
                log.TraceEvent(TraceEventType.Verbose, Thread.CurrentThread.ManagedThreadId, "ReceiveFromNegoStream: header (count==5)");
                h = new Header(b);
            }
            else
            {
                log.TraceEvent(TraceEventType.Verbose,Thread.CurrentThread.ManagedThreadId,"ReceiveFromNegoStream !=5");
                requeststatus.IsFirstRoundTrip = false;
                if (count != h.Length) throw new ArgumentException("ReceiveFromNegoStream: Not valid length");
                tokenToSend = new byte[count];
                Array.Copy(buffer, offset, tokenToSend, 0, count);
                log.TraceEvent(TraceEventType.Verbose, Thread.CurrentThread.ManagedThreadId, "ReceiveFromNegoStream: tokenToSend length :{0}", tokenToSend.Length);
            }
            log.TraceEvent(TraceEventType.Verbose, Thread.CurrentThread.ManagedThreadId, "ReceiveFromNegoStream: Synchronous return");

            return InsideAsyncResult.Synchronous(state);
        }
        public WindowsIdentity Identity
        { get { return identity; } }

        public Status State
        { get { return status; } }

        public void Dispose()
        {
            if (negostream != null) negostream.Dispose();
            if (identity != null) identity.Dispose();
        }
    }

}
